package 多线程.Volatile关键字;

/**
 * @author jing
 * @time 2017/9/1.
 */
/*
Java内存模型只保证了基本读取和赋值（而且必须是将数字赋值给某个变量，变量之间的相互赋值不是原子操作）是原子性操作，
如果要实现更大范围操作的原子性，可以通过synchronized和Lock来实现。
由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块，那么自然就不存在原子性问题了，从而保证了原子性。
如：
x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4
其实只有语句1是原子性操作，其他三个语句都不是原子性操作。
　　语句1是直接将数值10赋值给x，也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
　　语句2实际上包含2个操作，它先要去读取x的值(从物理内存读)，再将x的值写入工作内存(即CPU高速缓存)，
         虽然读取x的值以及将x的值写入工作内存，这2个操作都是原子性操作，但是合起来就不是原子性操作了。
　　同样的，x++和 x = x+1包括3个操作：读取x的值，进行加1操作，写入新的值。
 */
public class volatile不保证变量操作的原子性 {
    public volatile int inc = 0;  //volatile也不能保证线程的原子性，只能保证变量的
//    public  int inc = 0;

//    public synchronized void increase() {
    public  void increase() {
        inc++;
        System.out.println(inc+"_"+Thread.currentThread());
    }

    public static void main(String[] args) {
        final volatile不保证变量操作的原子性 test = new volatile不保证变量操作的原子性();
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        /*
                        try {
                            Thread.sleep(1); //线程休眠1ms
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //休眠打开后，最终inc很小，约10，是因为最开始休眠的线程，取得的inc很小，计算完后写回到内存的值
                        //覆盖了后面计算的较大的值。
                        */
                        test.increase();
                }

                ;
            }.start();
        }

        //保证前面开启的10个的线程都执行完
        while(Thread.activeCount()>2)  //activeCount返回当前线程的线程组中活动线程的数目
        {
//            System.out.println("当前存活线程"+Thread.activeCount()+"个，获得时间片的线程为"+Thread.currentThread());
            //Thread.yield(); //暂停当前正在执行的线程对象，并执行其他线程。
        }

        //实验发现，有安全问题：在线程sleep()没有使用的情况下，有很大概率计算出9999
        System.out.println("多线程计算最终结果："+test.inc);

        /*
        为什么最后存活了两个线程？为了搞清楚这个问题，设计以下程序来显示最后一直顽强存活的线程。
        .....
        实验发现，最终存活的两个线程如下：
        主线程和一个监控线程
        Thread[main,5,main]
        Thread[Monitor Ctrl-Break,5,main]
         */
        System.out.println("最后存活的不死线程");
        Thread[] threads =new Thread[2]; //存放最后存活的线程
        if(Thread.activeCount()==2){
            Thread.enumerate(threads);
        }
        for (Thread thread : threads) {
            System.out.println(thread);
        }

        /*
        多线程计算最终结果出问题分析：
        假如某个时刻变量inc的值为10，
　　     线程1对变量进行自增操作，线程1先读取了变量inc的原始值，然后线程1被阻塞了；
　　    然后线程2对变量进行自增操作，线程2也去读取变量inc的原始值，由于线程1只是对变量inc进行读取操作，
            而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量inc的缓存行无效，所以线程2会直接去主存读取inc的值，
            发现inc的值时10，然后进行加1操作，并把11写入工作内存，最后写入主存。
　　    然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，
             所以线程1对inc进行加1操作后inc的值为11，然后将11写入工作内存，最后写入主存。
　　    那么两个线程分别进行了一次自增操作后，inc只增加了1。

        而又volatile也不能解决线程安全问题的原因在于，线程1对变量进行读取操作之后，被阻塞了的话，并没有对inc值进行修改。
        然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值。
         */

    }
}
